/*
 *
 *  * Copyright:  2018 smarabbit studio.
 *  *
 *  *  Licensed under the Confluent Community License; you may not use this file
 *  *  except in compliance with the License.  You may obtain a copy of the License at
 *  *
 *  *  http://www.confluent.io/confluent-community-license
 *  *
 *  *  Unless required by applicable law or agreed to in writing, software
 *  *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  *  specific language governing permissions and limitations under the License.
 *  *
 *  *  @作   者： 黄开晖 (117227773@qq.com)
 *  *  @日   期:    ${DATE}
 *
 */

package com.massyframework.beanskin.runtime.service.instrument;

import java.lang.instrument.ClassDefinition;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.util.jar.JarFile;

import com.massyframework.beanskin.service.secure.SecureCapacity;

/**
 * {@link InstrumentationWrapper}, 封装从jvm获取的{@link Instrumentation}
 */
final class InstrumentationWrapper implements Instrumentation{

    private final Instrumentation target;

    private ClassFileTransformerCollection notRetransform;
    private ClassFileTransformerCollection canRetransform;


    /**
     * 构造方法
     */
    public InstrumentationWrapper(Instrumentation target) {
        if (target == null){
            throw new IllegalArgumentException("target cannot be null.");
        }
        this.target = target;
        this.notRetransform = new ClassFileTransformerCollection();
        this.canRetransform = new ClassFileTransformerCollection();

        this.target.addTransformer(this.notRetransform);
        this.target.addTransformer(this.canRetransform, true);
    }

    /* (non-Javadoc)
     * @see java.lang.instrument.Instrumentation#addTransformer(java.lang.instrument.ClassFileTransformer, boolean)
     */
    public void addTransformer(ClassFileTransformer transformer, boolean canRetransform) {
        if (transformer != null){
            if (canRetransform) {
            	this.canRetransform.addClassFileTransformer(transformer);
            }else {
            	this.notRetransform.addClassFileTransformer(transformer);
            }
        }
    }

    /* (non-Javadoc)
     * @see java.lang.instrument.Instrumentation#addTransformer(java.lang.instrument.ClassFileTransformer)
     */
    public void addTransformer(ClassFileTransformer transformer) {
        this.addTransformer(transformer, true);
    }

    /* (non-Javadoc)
     * @see java.lang.instrument.Instrumentation#removeTransformer(java.lang.instrument.ClassFileTransformer)
     */
    public boolean removeTransformer(ClassFileTransformer transformer) {
        boolean result = this.notRetransform.removeClassFileTransformer(transformer);
        if (!result) {
        	result = this.canRetransform.removeClassFileTransformer(transformer);
        }
        
        return result;
    }

    /* (non-Javadoc)
     * @see java.lang.instrument.Instrumentation#isRetransformClassesSupported()
     */
    public boolean isRetransformClassesSupported() {
        return this.target.isRedefineClassesSupported();
    }

    /* (non-Javadoc)
     * @see java.lang.instrument.Instrumentation#retransformClasses(java.lang.Class[])
     */
    public void retransformClasses(Class<?>... classes) throws UnmodifiableClassException {
        this.target.retransformClasses(classes);
    }

    /* (non-Javadoc)
     * @see java.lang.instrument.Instrumentation#isRedefineClassesSupported()
     */
    public boolean isRedefineClassesSupported() {
        return this.target.isRedefineClassesSupported();
    }

    /* (non-Javadoc)
     * @see java.lang.instrument.Instrumentation#redefineClasses(java.lang.instrument.ClassDefinition[])
     */
    public void redefineClasses(ClassDefinition... definitions)
            throws ClassNotFoundException, UnmodifiableClassException {
        this.target.redefineClasses(definitions);
    }

    /* (non-Javadoc)
     * @see java.lang.instrument.Instrumentation#isModifiableClass(java.lang.Class)
     */
    public boolean isModifiableClass(Class<?> theClass) {
    	ClassLoader loader = theClass.getClassLoader();
    	if (loader != null) {
    		if (loader instanceof SecureCapacity) {
    			if (((SecureCapacity)loader).isDecryptClass(theClass.getName())){
    				return false;
    			}
    		}
    	}
        return this.target.isModifiableClass(theClass);
    }

    /* (non-Javadoc)
     * @see java.lang.instrument.Instrumentation#getAllLoadedClasses()
     */
    @SuppressWarnings("rawtypes")
    public Class[] getAllLoadedClasses() {
        return this.target.getAllLoadedClasses();
    }

    /* (non-Javadoc)
     * @see java.lang.instrument.Instrumentation#getInitiatedClasses(java.lang.ClassLoader)
     */
    @SuppressWarnings("rawtypes")
    public Class[] getInitiatedClasses(ClassLoader loader) {
        return this.target.getInitiatedClasses(loader);
    }

    /* (non-Javadoc)
     * @see java.lang.instrument.Instrumentation#getObjectSize(java.lang.Object)
     */
    public long getObjectSize(Object objectToSize) {
        return this.target.getObjectSize(objectToSize);
    }

    /* (non-Javadoc)
     * @see java.lang.instrument.Instrumentation#appendToBootstrapClassLoaderSearch(java.util.jar.JarFile)
     */
    public void appendToBootstrapClassLoaderSearch(JarFile jarfile) {
        this.target.appendToBootstrapClassLoaderSearch(jarfile);
    }

    /* (non-Javadoc)
     * @see java.lang.instrument.Instrumentation#appendToSystemClassLoaderSearch(java.util.jar.JarFile)
     */
    public void appendToSystemClassLoaderSearch(JarFile jarfile) {
        this.target.appendToSystemClassLoaderSearch(jarfile);
    }

    /* (non-Javadoc)
     * @see java.lang.instrument.Instrumentation#isNativeMethodPrefixSupported()
     */
    public boolean isNativeMethodPrefixSupported() {
        return this.target.isNativeMethodPrefixSupported();
    }

    /* (non-Javadoc)
     * @see java.lang.instrument.Instrumentation#setNativeMethodPrefix(java.lang.instrument.ClassFileTransformer, java.lang.String)
     */
    public void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix) {
        this.target.setNativeMethodPrefix(transformer, prefix);
    }

	/**
     * 清除所有类文件转换器
     */
    public void clean(){
    	this.target.removeTransformer(this.notRetransform);
        this.notRetransform.clean();
        
        this.target.removeTransformer(this.canRetransform);
        this.canRetransform.clean();
    }
    
    Instrumentation getTargetInstrumentation(){
        return this.target;
    }
}
